/**
 * <p>Project: idcenter</p>
 * <p>Copyright: Copyright (c) 2019 daervin.com</p>
 */
package com.daervin.idcenter;

import java.util.concurrent.TimeUnit;

/**
 * +-------+---------------------+-----------------+---------------+
 * | sign  |  increment seconds |    node idcenter      |    sequence    |
 * +-------+--------------------+-----------------+---------------+
 * |   1   |         28         |      22         |      13       |
 * +-------+--------------------+-----------------+---------------+
 *
 * @author zscao
 */
public class SnowflakeUtil {

    private long epoch = 1553670776337L;
    private long epochSeconds = TimeUnit.MILLISECONDS.toSeconds(epoch);

    private int timesBits = 28;

    private int workerNodeBits = 22;

    private int sequenceBits = 13;

    private int workerId;

    private long lastTime = 0;

    private long sequence = 0;

    private BitsConfig config;

    public SnowflakeUtil(int workerId) {
        this.workerId = workerId;
        this.config = new BitsConfig(timesBits, workerNodeBits, sequenceBits);
    }

    public SnowflakeUtil(long epoch, int timesBits, int workerNodeBits, int sequenceBits) {
        this.epoch = epoch;
        this.timesBits = timesBits;
        this.workerNodeBits = workerNodeBits;
        this.sequenceBits = sequenceBits;
        this.config = new BitsConfig(timesBits, workerNodeBits, sequenceBits);
    }

    public SnowflakeUtil(long epoch, int timesBits, int workerNodeBits, int sequenceBits, BitsConfig config) {
        this.epoch = epoch;
        this.timesBits = timesBits;
        this.workerNodeBits = workerNodeBits;
        this.sequenceBits = sequenceBits;
        this.config = config;
    }

    public synchronized long nextId() {
        long currentTime = getCurrentTime();

        if (currentTime < lastTime) {
            throw new RuntimeException("当前时间戳比上次时间戳小");
        }
        if (currentTime == lastTime) {
            sequence = (sequence + 1) & config.getMaxSequence();
            //sequence为0说明已达到该秒的最大值，需要阻塞到下一秒
            if (sequence == 0) {
                currentTime = blocking(lastTime);
            }
        } else {
            sequence = 0;
        }

        lastTime = currentTime;

        return allocate(currentTime - epochSeconds, workerId, sequence);
    }

    private long getCurrentTime() {
        long currentSecond = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
        if (currentSecond - epochSeconds > config.getMaxSeconds()) {
            throw new RuntimeException("超过时间标志位的位数");
        }
        return currentSecond;
    }

    private long blocking(long lastTime) {
        while (true) {
            long nextTime = getCurrentTime();
            if (nextTime > lastTime) {
                return nextTime;
            }
        }
    }

    private long allocate(long seconds, long workerId, long sequence) {
        return (seconds << config.getTimestampShift()) | (workerId << config.getWorkerIdShift()) | sequence;
    }
}
